#include "stdafx.h"
#include "CppUnitTest.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

#include <vex/functional/make_raw_delegate.h>
#include <vex/functional/make_unique_delegate.h>
#include <vex/functional/make_intrusive_delegate.h>
#include <vex/functional/make_any_delegate.h>

namespace tests { namespace tests_functional_make_delegate {		

    namespace vf = vex::functional;
    namespace mpl = boost::mpl;
    namespace bft = boost::function_types;

    int namespace_add(int a, int b) { return a + b; }
    static int s_value = 0;
    int namespace_nullary() { return ++s_value; }

    TEST_CLASS(test_class)
    {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.functional.make_delegate")
        END_TEST_CLASS_ATTRIBUTE();
        int m_value;
    public:
        int member_nullary() { return ++m_value; }
        static int static_member_nullary() { return ++s_value; }

        TEST_METHOD(nullary_delegate_behaves_as_wrapped_function)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            s_value = m_value = 0;
            { // function l-value reference
                auto d = vf::make_any_delegate<int ()>(
                    namespace_nullary);
                Assert::IsTrue(d);
                Assert::AreEqual(1, d());
            }

            s_value = m_value = 0;
            { // function l-value reference
                auto d = vf::make_any_delegate<int ()>(
                    static_member_nullary);
                Assert::IsTrue(d);
                Assert::AreEqual(1, d());
            }

            s_value = m_value = 0;
            { // functor r-value
                auto d = vf::make_any_delegate<int ()>(
                    std::bind(&test_class::member_nullary, this));
                Assert::IsTrue(d);
                Assert::AreEqual(1, d());
            }

            s_value = m_value = 0;
            { // functor l-value
                auto f = std::bind(&test_class::member_nullary, this);
                auto d = vf::make_any_delegate<int ()>(f);
                Assert::IsTrue(d);
                Assert::AreEqual(1, d());
            }

            s_value = m_value = 0;
            { // lambda r-value
                auto d = vf::make_any_delegate<int ()>(
                    []() -> int { return 42; });
                Assert::IsTrue(d);
                Assert::AreEqual(42, d());
            }

            s_value = m_value = 0;
            { // lambda r-value with closure
                auto d = vf::make_any_delegate<int ()>(
                    [&]() -> int { return ++m_value; });
                Assert::IsTrue(d);
                Assert::AreEqual(1, d());
            }

            s_value = m_value = 0;
            { // lambda l-value with closure
                auto f = [&]() -> int { return ++s_value; };
                auto d = vf::make_any_delegate<int ()>(f);
                Assert::IsTrue(d);
                Assert::AreEqual(1, d());
            }
        }

        int member_add(int a, int b) const { return a + b; }
        static int static_member_add(int a, int b) { return a + b; }

        struct movable_non_copyable_functor_add_traits {
            typedef std::function<int (int, int)> item_type;
        };

#ifndef VEX_HAS_CPP_11_FEATURE_SET_3
        class movable_non_copyable_functor_add 
            : boost::noncopyable
            , public vex::policy::bool_convertible<
                movable_non_copyable_functor_add, 
                movable_non_copyable_functor_add_traits
            >
        {
            std::function<int (int, int)> m_func;
        public:
            typedef int result_type;
            int operator()(int a, int b) const
            {
                return m_func(a, b);
            }

            explicit movable_non_copyable_functor_add(std::function<int (int, int)> p_func)
                : m_func(std::move(p_func)) {}

            movable_non_copyable_functor_add(movable_non_copyable_functor_add&& p_other)
                : m_func(std::move(p_other.m_func)) 
            {
            }

            std::function<int (int, int)> get() const { return m_func; }
        };

        TEST_METHOD(binary_delegate_from_movable_non_copyable_functor_behaves_as_wrapped_function)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            { // movable, non_copyable functor l-value
                movable_non_copyable_functor_add f([=](int a, int b) { return this->member_add(a, b); });
                auto d = vf::make_any_delegate<int (int, int)>(std::move(f));
                Assert::IsFalse(f);
                Assert::IsTrue(d);
                Assert::AreEqual(43, d(42, 1));
            }
        }

        TEST_METHOD(binary_delegate_from_any_delegate_behaves_as_wrapped_function)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            { // movable, non_copyable functor l-value
                movable_non_copyable_functor_add f([=](int a, int b) { return this->member_add(a, b); });
                auto d1 = vf::make_any_delegate<int (int, int)>(std::move(f));
                Assert::IsFalse(f);
                Assert::IsTrue(d1);
                Assert::AreEqual(43, d1(42, 1));

                auto d2 = vf::make_any_delegate<int (int, int)>(std::move(d1));
                Assert::IsFalse(d1);
                Assert::AreEqual(43, d2(42, 1));
            }
        }
#else
#pragma message("Testcase with movable_non_copyable_functor_add does not work without perfect forwarding!")
#endif

        TEST_METHOD(binary_delegate_behaves_as_wrapped_function)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();

            { // function l-value reference
                auto d = vf::make_any_delegate<int (int, int)>(
                    namespace_add);
                Assert::IsTrue(d);
                Assert::AreEqual(43, d(42, 1));
            }

            { // function l-value reference
                auto d = vf::make_any_delegate<int (int, int)>(
                    static_member_add);
                Assert::IsTrue(d);
                Assert::AreEqual(43, d(42, 1));
            }

            { // functor r-value
                auto d = vf::make_any_delegate<int (int, int)>(
                    std::bind(&test_class::member_add, this, std::placeholders::_1, std::placeholders::_2));
                Assert::IsTrue(d);
                Assert::AreEqual(43, d(42, 1));
            }

            { // functor l-value
                auto f = std::bind(&test_class::member_add, this, std::placeholders::_1, std::placeholders::_2);
                auto d = vf::make_any_delegate<int (int, int)>(f);
                Assert::IsTrue(d);
                Assert::AreEqual(43, d(42, 1));
            }

            { // lambda member l-value
                auto f = [=](int a, int b) { return this->member_add(a, b); };
                auto d = vf::make_any_delegate<int (int, int)>(f);
                Assert::IsTrue(d);
                Assert::AreEqual(43, d(42, 1));
                Assert::AreEqual(42, d(-1, 43));
            }

            { // lambda member r-value
                auto d = vf::make_any_delegate<int (int, int)>([=](int a, int b) { 
                    return this->member_add(a, b); 
                });
                Assert::IsTrue(d);
                Assert::AreEqual(43, d(42, 1));
                Assert::AreEqual(42, d(-1, 43));
            }

            { // lambda r-value
                auto d = vf::make_any_delegate<int (int, int)>(
                    [](int a, int b) { return a + b; });
                Assert::IsTrue(d);
                Assert::AreEqual(43, d(42, 1));
            }

            { // lambda r-value with closure
                int t_value = 42;
                auto d = vf::make_any_delegate<int (int, int)>(
                    [t_value](int a, int b) { return t_value + a - b; });
                Assert::IsTrue(d);
                Assert::AreEqual(43, d(1, 0));
            }

            { // lambda l-value
                auto f = [](int a, int b) { return a + b; };
                auto d = vf::make_any_delegate<int (int, int)>(f);
                Assert::IsTrue(d);
                Assert::AreEqual(43, d(42, 1));
            }
        }
    };
}}